package com.nulldev.util.web.nTube_DL;

import java.util.Map;

import com.nulldev.util.JVM.Arguments;
import com.nulldev.util.JVM.Arguments.ArgumentInit;
import com.nulldev.util.JVM.Arguments.ParsedArguments;
import com.nulldev.util.JVM.JVM;
import com.nulldev.util.JVM.arguments.Argument;
import com.nulldev.util.JVM.arguments.ArgumentWithValue;
import com.nulldev.util.concurrency.threadIt.v4.emapi.ExecutorManager;
import com.nulldev.util.concurrency.threadIt.v4.emapi.uft.Executable;
import com.nulldev.util.data.Variables;
import com.nulldev.util.data.Arrays.maps.FastMaps;
import com.nulldev.util.io.FastIO.FastIO;
import com.nulldev.util.io.file.FileUtil;
import com.nulldev.util.web.URLUtils.URLQueryParser;
import com.nulldev.util.web.YouTubeExtractor.YouTubeExtractor;
import com.nulldev.util.web.YouTubeExtractor.YouTubeExtractor.ExtractorType;
import com.nulldev.util.web.YouTubeExtractor.extractors.IYouTubeExtractorConstants;
import com.nulldev.util.web.YouTubeExtractor.formats.Format;
import com.nulldev.util.web.YouTubeExtractor.video.ExtractedVideo;
import com.nulldev.util.web.YouTubeExtractor.video.YouTubeVideo;
import com.nulldev.util.web.nTube.NTubeServer;

public class nTubeDL {

	private static final String VERSION = "1.0.0";
	/* 1920x1080, 30 FPS, H.264/MP4 */
	private static final int DEFAULT_VIDEO_ITAG = 137;
	/* 128kbps Opus (WebM) */
	private static final int DEFAULT_AUDIO_ITAG = 251;

	/* Arguments */

	public static final Argument arg_version = new Argument(new String[]
		{ "--version", "-v" }, "Show version and exit.", true);
	public static final Argument list_formats = new Argument(new String[]
		{ "--list-formats", "-lf" }, "List available formats and exit.", true);
	public static final ArgumentWithValue<String> extractor_type = new ArgumentWithValue.StringArgument(new String[]
		{ "--etype", "-etype" }, "Sets the extractor type.", "One of the following types: (page, embed, age_gate, tp)", true);
	public static final ArgumentWithValue<Boolean> enable_clarifications = new ArgumentWithValue.BooleanArgument(new String[]
		{ "--enable-clarifications", "-clarifications" }, "Enables extracting clarifications (experimental!)", "", true);

	/* */

	public static void main(String[] args) {
		setup_args();
		final ParsedArguments pa = Arguments.setup(args);
		if (pa.has(arg_version)) {
			JVM.println("Printing versions.");
			JVM.println("\tnTube Version: " + NTubeServer.nTube_RELEASE_STRING);
			JVM.println("\tnTube-DL Version: " + VERSION);
			JVM.println("\tYTE Version: " + YouTubeExtractor.VERSION);
			JVM.println("Exiting.");
			FastIO.get().flush();
			JVM.exit();
		}
		ExtractorType et = ExtractorType.PAGE;
		boolean enable_clarifications = false;
		if (pa.has(extractor_type)) {
			final String type = pa.getValue(extractor_type);
			et = ExtractorType.get(type);
		}
		String url = null;
		for (final String arg : args) {
			if (arg.startsWith("https://www.youtube.com/watch?v=")) {
				url = arg;
				break;
			} else if (arg.startsWith("http://127.0.0.1:8080/watch?v=")) {
				url = arg.replace("http://127.0.0.1:8080/", "https://www.youtube.com/");
				break;
			}
		}
		if (url == null) {
			JVM.println("[!] No URL found, exiting.");
			FastIO.get().flush();
			JVM.exit();
		}
		JVM.println("Fetching video: " + url);
		final Map<String, String> url_args = URLQueryParser.extract(url);
		if (!url_args.containsKey("v")) {
			JVM.println("[!] No video query found, exiting.");
			FastIO.get().flush();
			JVM.exit();
		}
		final Executable<ExtractedVideo> ex = YouTubeExtractor.get(et).generateTask(FastMaps.fixedUniMap(new Object[][]
			{
					{ IYouTubeExtractorConstants.MAP_TYPE_VIDEO_ID, url_args.get("v") },
					{ IYouTubeExtractorConstants.MAP_TYPE_ENABLE_LATE_EXTRACTIONS, false },
					{ IYouTubeExtractorConstants.MAP_TYPE_ENABLE_CLARIFICATIONS, enable_clarifications } }));
		ExecutorManager.get().asyncSubmit(ex);
		JVM.println("--> Awaiting extraction.");
		final ExtractedVideo vid = ex.waitTillCompletion();
		if (!vid.getResult().successful()) {
			JVM.println("[!] Failed to extract video.");
			JVM.println("Result: " + vid.getResult());
			FastIO.get().flush();
			JVM.exit();
		}
		if (vid.supportsLateExtraction()) {
			JVM.println("[i] Late extraction is enabled, waiting even more.");
			vid.awaitCompletion();
		}
		JVM.println("\tVideo Title: " + vid.getMetadata().getTitle());
		JVM.println("\tVideo Author: " + vid.getMetadata().getAuthor());
		if (pa.has(list_formats)) {
			__list_formats(vid);
		} else {
			__download_itags(vid, DEFAULT_VIDEO_ITAG, DEFAULT_AUDIO_ITAG);
		}
	}

	private static void __download_itags(final ExtractedVideo vid, final int... itags) {
		if (itags == null || itags.length == 0) {
			JVM.println("[!] No itags provided, exiting.");
			FastIO.get().flush();
			JVM.exit();
			return;
		} else if (itags.length == 1) {
			/* TODO */
		} else {
			final YouTubeVideo primary = vid.videos().stream().filter((video) -> video.getTag() == itags[0]).findFirst().get();
			Variables.requireNonNull(primary, "primary");
			final YouTubeVideo secondary = vid.videos().stream().filter((video) -> video.getTag() == itags[1]).findFirst().get();
			Variables.requireNonNull(secondary, "secondary");
		}
	}

	/* FIXME: Tabs are broken? */
	private static final String SPACER = "\t";
	private static void __list_formats(final ExtractedVideo vid) {
		JVM.println("Listing formats...");
		JVM.println("itag" + SPACER + "fps" + SPACER + "width" + SPACER + "height" + SPACER + "bitrate" + SPACER + "fileType" + SPACER + "size");
		JVM.println("==============================================================");
		for (final YouTubeVideo o : vid.videos()) {
			final Format fmt = o.getFormat();
			JVM.println(o.getTag() + SPACER + o.getFPS() + SPACER + fmt.getWidth() + SPACER + fmt.getHeight() + SPACER
					+ FileUtil.convertToStringRepresentation(o.getBitrate(), true) + SPACER + fmt.getFileType() + SPACER
					+ FileUtil.convertToStringRepresentation(o.getFileSize()));
		}
		FastIO.get().flush();
		JVM.exit();
		return;
	}

	private static void setup_args() {
		Arguments.argumentInit(new ArgumentInit() {

			@Override
			public void onInit(final Arguments args) {
				args.addArgument(arg_version);
				args.addArgument(extractor_type);
				args.addArgument(enable_clarifications);
				args.addArgument(list_formats);
			}
		});
	}

}
